package com.tsing.cedap.rest.service.impl;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.jwt.JWT;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
import com.aliyun.teaopenapi.models.Config;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.tsing.cedap.common.config.RedisManager;
import com.tsing.cedap.common.constants.AuthConstant;
import com.tsing.cedap.common.exceptions.BusinessException;
import com.tsing.cedap.common.exceptions.CommonException;
import com.tsing.cedap.common.utils.GetLoginUserUtil;
import com.tsing.cedap.common.utils.MD5Util;
import com.tsing.cedap.common.vo.UserVo;
import com.tsing.cedap.rest.entity.OrderEntity;
import com.tsing.cedap.rest.entity.UserAuthEntity;
import com.tsing.cedap.rest.entity.UserEntity;
import com.tsing.cedap.rest.entity.WalletEntity;
import com.tsing.cedap.rest.mapper.OrderMapper;
import com.tsing.cedap.rest.mapper.UserAuthMapper;
import com.tsing.cedap.rest.mapper.UserMapper;
import com.tsing.cedap.rest.mapper.WalletMapper;
import com.tsing.cedap.rest.service.IUserService;
import com.tsing.cedap.rest.vo.reqVo.CodeReqVo;
import com.tsing.cedap.rest.vo.reqVo.MiniRegisterReqVo;
import com.tsing.cedap.rest.vo.reqVo.PerfectUserInfoReqVo;
import com.tsing.cedap.rest.vo.reqVo.VerifyCodeReqVo;
import com.tsing.cedap.rest.vo.respVo.LoginRespVo;
import com.tsing.cedap.rest.vo.respVo.MiniIndexStatisticRespVo;
import com.tsing.cedap.rest.vo.respVo.SendMessageRespVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author TheTsing
 * @since 2022-01-10
 */
@Service
public class UserServiceImpl implements IUserService {

    @Value("${wxMini.appid}")
    private String appid;

    @Value("${wxMini.secret}")
    private String secret;

    @Value("${frame.tokenExpire.time:3600}")
    private Long tokenExpireTime;

    @Value("${spring.cloud.alicloud.access-key}")
    private String accessKeyId;

    @Value("${spring.cloud.alicloud.secret-key}")
    private String accessKeySecret;

    @Value("${spring.cloud.alicloud.sms.endpoint}")
    private String endpoint;

    @Value("${spring.cloud.alicloud.sms.signName}")
    private String signName;

    @Value("${spring.cloud.alicloud.sms.templateCode}")
    private String templateCode;

    @Resource
    private UserMapper userMapper;

    @Resource
    private UserAuthMapper userAuthMapper;

    @Resource
    private WalletMapper walletMapper;

    @Resource
    private OrderMapper orderMapper;

    @Autowired
    private RedisManager redisManager;

    @Override
    public LoginRespVo miniLogin(CodeReqVo reqVo) {
        String openid = getOpenid(reqVo.getCode());
        UserAuthEntity userAuthEntity = new LambdaQueryChainWrapper<>(userAuthMapper)
                .select(UserAuthEntity::getUserId)
                .eq(UserAuthEntity::getIdentityType, 3)
                .eq(UserAuthEntity::getIdentifier, openid)
                .last("LIMIT 1")
                .one();
        if (ObjectUtil.isNull(userAuthEntity)) {
            throw new BusinessException(CommonException.Proxy.USER_NOT_EXIST);
        }
        if (!new LambdaQueryChainWrapper<>(userMapper)
                .select(UserEntity::getEnabled)
                .eq(UserEntity::getId, userAuthEntity.getUserId())
                .last("LIMIT 1")
                .one()
                .getEnabled()) {
            throw new BusinessException(CommonException.Proxy.USER_HAS_BEEN_DISABLED);
        }
        return new LoginRespVo().setToken(getToken(userAuthEntity.getUserId()));
    }

    private String getToken(String id) {
        //限制账号同时只能在一处登录
        String token = redisManager.getString(AuthConstant.USER_TOKEN_PREFIX + id);
        if (StrUtil.isNotBlank(token)) {
            redisManager.deleteString(token);
        }
        UserVo userVo = new UserVo()
                .setId(id)
                .setToken(JWT
                        .create()
                        .setPayload(IdUtil.fastSimpleUUID(), id + Instant.now().toEpochMilli())
                        .setKey(id.getBytes(StandardCharsets.UTF_8))
                        .sign())
                .setExpire(tokenExpireTime);
        redisManager.setString(userVo.getToken(), JSONObject.toJSONString(userVo), userVo.getExpire());
        redisManager.setString(AuthConstant.USER_TOKEN_PREFIX + id, userVo.getToken(), userVo.getExpire());
        return userVo.getToken();
    }

    private String getOpenid(String code) {
        HashMap<String, Object> paramMap = new HashMap<>(8);
        paramMap.put("appid", appid);
        paramMap.put("secret", secret);
        paramMap.put("js_code", code);
        paramMap.put("grant_type", "authorization_code");
        JSONObject resultObj = JSONObject.parseObject(HttpUtil.get("https://api.weixin.qq.com/sns/jscode2session", paramMap));
        String openid = resultObj.getString("openid");
        if (StrUtil.isBlank(openid)) {
            throw new BusinessException(CommonException.Proxy.WECHAT_SERVER_EXCEPTION);
        }
        return openid;
        //return "aaa";
    }

    @Override
    public SendMessageRespVo miniSendMessage(CodeReqVo reqVo) {
        UserEntity userEntity = new LambdaQueryChainWrapper<>(userMapper)
                .select(UserEntity::getEnabled)
                .eq(UserEntity::getPhone, reqVo.getCode())
                .last("LIMIT 1")
                .one();
        if (ObjectUtil.isNotNull(userEntity) && !userEntity.getEnabled()) {
            throw new BusinessException(CommonException.Proxy.USER_HAS_BEEN_DISABLED);
        }
        String code = RandomUtil.randomNumbers(6);
        Config config = new Config()
                .setAccessKeyId(accessKeyId)
                .setAccessKeySecret(accessKeySecret);
        config.endpoint = endpoint;
        SendSmsRequest sendSmsRequest = new SendSmsRequest()
                .setPhoneNumbers(reqVo.getCode())
                .setSignName(signName)
                .setTemplateCode(templateCode)
                .setTemplateParam("{\"code\":\"" + code + "\"}");
        SendSmsResponse sendSmsResponse;
        try {
            sendSmsResponse = new Client(config).sendSms(sendSmsRequest);
        } catch (Exception exception) {
            throw new BusinessException(CommonException.Proxy.MESSAGE_SEND_ERROR);
        }
        if (!"OK".equals(sendSmsResponse.body.getCode())) {
            throw new BusinessException(CommonException.Proxy.MESSAGE_SEND_ERROR);
        }
        String uuid = IdUtil.fastSimpleUUID();
        redisManager.setString(uuid, code, 600L);
        return new SendMessageRespVo().setUuid(uuid);
    }

    @Override
    public void verifyCode(VerifyCodeReqVo reqVo) {
        String code = redisManager.getString(reqVo.getUuid());
        if (StrUtil.isBlank(code) || !code.equals(reqVo.getCode())) {
            throw new BusinessException(CommonException.Proxy.MESSAGE_VERIFY_ERROR);
        } else {
            redisManager.deleteString(reqVo.getUuid());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public LoginRespVo miniRegister(MiniRegisterReqVo reqVo) {
        String openid = getOpenid(reqVo.getCode());
        if (ObjectUtil.isNotNull(new LambdaQueryChainWrapper<>(userAuthMapper)
                .select(UserAuthEntity::getId)
                .eq(UserAuthEntity::getIdentifier, openid)
                .last("LIMIT 1")
                .one())) {
            throw new BusinessException(CommonException.Proxy.USER_IS_EXIST);
        }
        UserEntity userEntity = new LambdaQueryChainWrapper<>(userMapper)
                .select(UserEntity::getId, UserEntity::getEnabled)
                .eq(UserEntity::getPhone, reqVo.getPhone())
                .last("LIMIT 1")
                .one();
        if (ObjectUtil.isNull(userEntity)) {
            userEntity = new UserEntity()
                    .setNickname(reqVo.getNickname())
                    .setPhone(reqVo.getPhone())
                    .setAvatar(reqVo.getAvatar())
                    .setEnabled(true)
                    .setRole(2);
            userMapper.insert(userEntity);
            userAuthMapper.insert(new UserAuthEntity()
                    .setUserId(userEntity.getId())
                    .setIdentityType(3)
                    .setIdentifier(openid));
            userAuthMapper.insert(new UserAuthEntity()
                    .setUserId(userEntity.getId())
                    .setIdentityType(1)
                    .setIdentifier(reqVo.getPhone())
                    .setCredential(MD5Util.getMD5ByStr(reqVo.getPhone().substring(reqVo.getPhone().length() - 6), AuthConstant.PASSWORD_MD5_COUNT)));
            walletMapper.insert(new WalletEntity()
                    .setUserId(userEntity.getId())
                    .setBalance(BigDecimal.ZERO));
        } else {
            if (!userEntity.getEnabled()) {
                throw new BusinessException(CommonException.Proxy.USER_HAS_BEEN_DISABLED);
            }
            userAuthMapper.insert(new UserAuthEntity()
                    .setUserId(userEntity.getId())
                    .setIdentityType(3)
                    .setIdentifier(openid));
        }
        return new LoginRespVo().setToken(getToken(userEntity.getId()));
    }

    @Override
    public void perfectUserInfo(PerfectUserInfoReqVo reqVo, HttpServletRequest request) {
        String token = request.getHeader(AuthConstant.AUTHENTICATION_HEAD);
        UserVo userVo = GetLoginUserUtil.getLoginUserInfo(token, redisManager);
        userMapper.updateById(new UserEntity()
                .setId(userVo.getId())
                .setName(reqVo.getName())
                .setCollegeId(reqVo.getCollegeId())
                .setNumber(reqVo.getNumber()));
    }

    @Override
    public MiniIndexStatisticRespVo miniCustomerStatistic(HttpServletRequest request) {
        String token = request.getHeader(AuthConstant.AUTHENTICATION_HEAD);
        UserVo userVo = GetLoginUserUtil.getLoginUserInfo(token, redisManager);
        List<OrderEntity> orderEntities = new LambdaQueryChainWrapper<>(orderMapper)
                .select(OrderEntity::getStatus)
                .eq(OrderEntity::getCustomerId, userVo.getId())
                .list();
        return miniStatistic(orderEntities);
    }

    @Override
    public MiniIndexStatisticRespVo miniRiderStatistic(HttpServletRequest request) {
        String token = request.getHeader(AuthConstant.AUTHENTICATION_HEAD);
        UserVo userVo = GetLoginUserUtil.getLoginUserInfo(token, redisManager);
        List<OrderEntity> orderEntities = new LambdaQueryChainWrapper<>(orderMapper)
                .select(OrderEntity::getStatus)
                .eq(OrderEntity::getRiderId, userVo.getId())
                .list();
        return miniStatistic(orderEntities);
    }

    private MiniIndexStatisticRespVo miniStatistic(List<OrderEntity> orderEntities) {
        MiniIndexStatisticRespVo miniIndexStatisticRespVo = new MiniIndexStatisticRespVo()
                .setWaitTake(0)
                .setWaitReceive(0)
                .setFinish(0);
        for (int i = 0, size = orderEntities.size(); i < size; i++) {
            switch (orderEntities.get(i).getStatus()) {
                case 1:
                    miniIndexStatisticRespVo.setWaitTake(miniIndexStatisticRespVo.getWaitTake() + 1);
                    break;
                case 2:
                    miniIndexStatisticRespVo.setWaitReceive(miniIndexStatisticRespVo.getWaitReceive() + 1);
                    break;
                case 4:
                    miniIndexStatisticRespVo.setFinish(miniIndexStatisticRespVo.getFinish() + 1);
                    break;
            }
        }
        return miniIndexStatisticRespVo;
    }

}
